package database

import (
	"context"        // 提供上下文功能，用于控制Redis操作的生命周期（超时、取消等）
	"fmt"            // 用于字符串格式化，构建Redis连接地址
	"gameServer/log" // 日志包，记录Redis操作相关日志
	"strconv"
	"time" // 提供时间相关功能，设置超时时间等

	"github.com/redis/go-redis/v9" // Redis官方推荐的Go客户端库，提供Redis操作的封装
)

/*
本文件实现了Redis数据库的具体操作，基于基础数据库接口（Database）扩展，
封装了Redis的连接、断开、键值操作、哈希操作等核心功能，适配Redis作为缓存或数据存储的场景。
*/

// RedisDatabase Redis数据库的具体实现结构体，
// 嵌入BaseDatabase继承配置管理功能，同时持有Redis客户端实例用于实际操作
type RedisDatabase struct {
	BaseDatabase               // 嵌入基础数据库结构体，继承配置获取等通用功能
	client       *redis.Client // Redis客户端实例，由go-redis库提供，封装了Redis的所有操作
}

// NewRedisDatabase 创建Redis数据库实例
func NewRedisDatabase(config *Config) *RedisDatabase {
	return &RedisDatabase{
		BaseDatabase: *NewBaseDatabase(config),
	}
}

// Connect 建立与Redis的连接，初始化客户端并测试连接有效性
// 步骤：构建连接配置 → 创建客户端实例 → 测试连接（带超时）→ 保存客户端实例
// 返回值：
//
//	error: 连接成功返回nil，失败返回具体错误（如网络问题、认证失败）
func (r *RedisDatabase) Connect() error {
	// 将数据库字符串转换为正数
	dbIndex, err := strconv.Atoi(r.config.Database)
	if err != nil {
		return fmt.Errorf("invalid Redis database index: %s", r.config.Database)
	}
	// 构建Redis连接配置，基于基础配置中的参数
	option := &redis.Options{
		Addr:     fmt.Sprintf("%s:%d", r.config.Host, r.config.Port), // 连接地址：主机+端口（如"localhost:6379"）
		Password: r.config.Password,                                  // 连接密码（Redis可配置密码认证，无密码则为空）
		DB:       dbIndex,                                            // 要连接的数据库编号（Redis默认有16个库，编号0-15）
		PoolSize: r.config.PoolSize,                                  // 连接池大小，控制并发连接数量
	}

	// 创建Redis客户端实例，基于上述配置
	client := redis.NewClient(option)

	// 创建一个5秒超时的上下文，用于控制Ping操作的超时（避免连接测试无限阻塞）
	// context.Background()是根上下文，WithTimeout在此基础上设置5秒超时
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel() // 函数退出时释放上下文资源，避免内存泄漏

	// 测试连接有效性：通过Ping命令确认客户端能否正常与Redis通信
	if err := client.Ping(ctx).Err(); err != nil {
		return fmt.Errorf("failed to ping Redis: %v", err) // 包装错误信息，便于排查
	}

	r.client = client // 保存客户端实例，供后续操作使用
	// 记录连接成功日志，包含连接的主机和端口
	log.Infof("Redis connected to %s:%d", r.config.Host, r.config.Port)
	return nil // 连接成功
}

// Disconnect 断开与Redis的连接，释放客户端资源
// 返回值：
//
//	error: 断开成功返回nil，失败返回错误信息（如客户端未初始化）
func (r *RedisDatabase) Disconnect() error {
	if r.client != nil { // 检查客户端实例是否存在
		return r.client.Close() // 关闭客户端，释放连接池资源
	}
	return nil // 客户端未初始化时，视为已断开，返回nil
}

// Ping 检测与Redis的连接是否有效，用于心跳检测或连接状态验证
// 返回值：
//
//	error: 连接有效返回nil；客户端未初始化返回ErrDBNotConnected；连接无效返回具体错误
func (r *RedisDatabase) Ping() error {
	if r.client == nil { // 客户端未初始化（未连接），返回"未连接"错误
		return ErrDBNotConnected
	}

	// 创建5秒超时的上下文，控制Ping操作的超时（避免长时间阻塞）
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel() // 释放上下文资源

	// 调用Redis客户端的Ping方法，通过上下文控制超时
	return r.client.Ping(ctx).Err()
}

// Type 返回当前数据库的类型，实现Database接口
// 返回值：
//
//	DBType: 固定返回DBTypeRedis，表示当前是Redis数据库
func (r *RedisDatabase) Type() DBType {
	return DBTypeRedis
}

// GetClient 获取底层的Redis客户端实例，允许上层代码进行更复杂的Redis操作（如事务、管道等）
// 返回值：
//
//	*redis.Client: Redis客户端实例；若未连接，返回nil
func (r *RedisDatabase) GetClient() *redis.Client {
	return r.client
}

// Set 向Redis中设置键值对，支持设置过期时间
// 参数：
//
//	key: 键名（Redis中用于标识数据的唯一字符串）
//	value: 键值（支持多种类型，如字符串、数字等，go-redis会自动序列化）
//	expiration: 过期时间（如10*time.Second表示10秒后过期，0表示永不过期）
//
// 返回值：
//
//	error: 操作成功返回nil；未连接返回ErrDBNotConnected；失败返回具体错误
func (r *RedisDatabase) Set(key string, value interface{}, expiration time.Duration) error {
	if r.client == nil { // 检查连接状态
		return ErrDBNotConnected
	}
	ctx := context.Background() // 使用默认上下文
	// 使用传入的上下文而非默认背景上下文
	// 调用Redis客户端的Set方法，设置键值对并指定过期时间
	return r.client.Set(ctx, key, value, expiration).Err()
}

// Get 从Redis中获取指定键的值
// 参数：
//
//	key: 要查询的键名
//
// 返回值：
//
//	string: 键对应的value（以字符串形式返回，需调用者自行转换类型）
//	error: 操作成功返回nil；未连接返回ErrDBNotConnected；键不存在返回redis.Nil错误
func (r *RedisDatabase) Get(key string) (string, error) {
	if r.client == nil { // 检查连接状态
		return "", ErrDBNotConnected
	}

	ctx := context.Background() // 使用默认上下文
	// 调用Redis客户端的Get方法，获取键值（返回结果包含value和可能的错误）
	return r.client.Get(ctx, key).Result()
}

// HSet 向Redis的哈希表（Hash）中设置字段和值（哈希表是Redis中存储键值对集合的结构）
// 参数：
//
//	key: 哈希表的键名（标识一个哈希表）
//	values: 可变参数，需为偶数个（field1, value1, field2, value2...），表示要设置的字段和值
//
// 返回值：
//
//	error: 操作成功返回nil；未连接返回ErrDBNotConnected；失败返回具体错误
func (r *RedisDatabase) HSet(key string, values ...interface{}) error {
	if r.client == nil { // 检查连接状态
		return ErrDBNotConnected
	}

	ctx := context.Background() // 使用默认上下文
	// 调用Redis客户端的HSet方法，向哈希表中设置字段值
	return r.client.HSet(ctx, key, values...).Err()
}

// HGet 从Redis的哈希表中获取指定字段的值
// 参数：
//
//	key: 哈希表的键名
//	field: 要查询的字段名
//
// 返回值：
//
//	string: 字段对应的value（字符串形式）
//	error: 操作成功返回nil；未连接返回ErrDBNotConnected；字段不存在返回redis.Nil错误
func (r *RedisDatabase) HGet(key, field string) (string, error) {
	if r.client == nil { // 检查连接状态
		return "", ErrDBNotConnected
	}

	ctx := context.Background() // 使用默认上下文
	// 调用Redis客户端的HGet方法，获取哈希表中指定字段的值
	return r.client.HGet(ctx, key, field).Result()
}

// HGetAll 从Redis的哈希表中获取所有字段和值
// 参数：
//
//	key: 哈希表的键名
//
// 返回值：
//
//	map[string]string: 包含所有字段和值的映射（field为key，value为对应值）
//	error: 操作成功返回nil；未连接返回ErrDBNotConnected；失败返回具体错误
func (r *RedisDatabase) HGetAll(key string) (map[string]string, error) {
	if r.client == nil { // 检查连接状态
		return nil, ErrDBNotConnected
	}

	ctx := context.Background() // 使用默认上下文
	// 调用Redis客户端的HGetAll方法，获取哈希表中所有字段和值
	return r.client.HGetAll(ctx, key).Result()
}

// SAdd 向集合添加一个或多个成员
func (r *RedisDatabase) SAdd(ctx context.Context, key string, members ...interface{}) (int64, error) {
	if r.client == nil {
		return 0, ErrDBNotConnected
	}

	return r.client.SAdd(ctx, key, members...).Result()
}

// WithContext 在指定上下文中执行Redis操作，支持超时控制、操作取消等功能
// 参数：
//
//	ctx: 上下文对象（可带超时、取消信号等）
//	fn: 回调函数，接收*redis.Client实例，在函数内执行具体的Redis操作（需使用传入的ctx）
//
// 返回值：
//
//	error: 操作成功返回nil；未连接返回ErrDBNotConnected；失败返回具体错误
func (r *RedisDatabase) WithContext(ctx context.Context, fn func(*redis.Client) error) error {
	if r.client == nil { // 检查连接状态
		return ErrDBNotConnected
	}
	// 调用回调函数，将客户端实例传入，函数内可使用带ctx的方法（如client.Get(ctx, ...)）
	return fn(r.client)
}

// 设置带过期时间的键值对（会话专用）
func (r *RedisDatabase) SetEx(key string, value interface{}, expiration time.Duration) error {
	if r.client == nil {
		return ErrDBNotConnected
	}
	ctx := context.Background()
	return r.client.SetEx(ctx, key, value, expiration).Err()
}

// 删除键
func (r *RedisDatabase) Del(key string) error {
	if r.client == nil {
		return ErrDBNotConnected
	}
	ctx := context.Background()
	return r.client.Del(ctx, key).Err()
}

// 检查键是否存在
func (r *RedisDatabase) HMSet(key string, values ...interface{}) (bool, error) {
	if r.client == nil {
		return false, ErrDBNotConnected
	}
	ctx := context.Background()
	return r.client.HMSet(ctx, key, values...).Result()
}

// Exists 检查键是否存在
// 参数：
//   ctx: 上下文，用于控制超时和取消操作
//   key: 要检查的键名
// 返回值：
//   bool: 如果键存在返回true，否则返回false
//   error: 操作过程中遇到的错误，连接未建立时返回ErrDBNotConnected
func (r *RedisDatabase) Exists(ctx context.Context, key string) (bool, error) {
    // 检查客户端连接是否已建立
    if r.client == nil {
        return false, ErrDBNotConnected
    }
    
    // 调用Redis客户端的Exists方法，检查键是否存在
    // Exists命令返回键的数量，0表示不存在，>=1表示存在
    result, err := r.client.Exists(ctx, key).Result()
    if err != nil {
        return false, err
    }
    
    // 判断结果，大于0表示键存在
    return result > 0, nil
}
